#include "MysqlResult.h"
#include "exception/SQLException.h"
#include "../utility/format/Format.h"
#include <cstring>

using namespace std;
using namespace mysql;
using namespace format;

MysqlResult::MysqlResult(MYSQL_STMT *stmt) :
        stmt_(stmt), result_(nullptr), bind_(nullptr) {
    if (mysql_stmt_store_result(stmt)) {
        if (mysql_stmt_errno(stmt) != 0) {
            throw SQLException("Failed to store result set", mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
        }
    }
    result_ = mysql_stmt_result_metadata(stmt);
    if (!result_) {
        if (mysql_stmt_errno(stmt) != 0) {
            throw SQLException("Failed to get result metadata", mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
        }
    }
    unsigned long num_fields = mysql_num_fields(result_);
    bind_ = new MYSQL_BIND[num_fields];
    memset(bind_, 0, sizeof(MYSQL_BIND) * num_fields);
    MYSQL_FIELD *fields = mysql_fetch_fields(result_);
    for (unsigned long i = 0; i < num_fields; ++i) {
        bind_[i].buffer_type = fields[i].type;
        bind_[i].is_null = new char;
        switch (fields[i].type) {
            case MYSQL_TYPE_STRING:
            case MYSQL_TYPE_VAR_STRING:
                bind_[i].buffer = new char[fields[i].length + 1];
                bind_[i].buffer_length = fields[i].length + 1;
                break;
            case MYSQL_TYPE_LONG:
                bind_[i].buffer = new int;
                break;
            case MYSQL_TYPE_TIMESTAMP:
            case MYSQL_TYPE_DATETIME:
                bind_[i].buffer = new MYSQL_TIME;
                break;
            default:
                throw SQLException("Unsupported column type", 0);
        }
    }
    if (mysql_stmt_bind_result(stmt_, bind_)) {
        throw SQLException("Failed to bind result", mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
    }
}

MysqlResult::~MysqlResult() {
    if (result_ != nullptr) {
        mysql_free_result(result_);
    }
    if (bind_ != nullptr) {
        unsigned long num_fields = mysql_num_fields(result_);
        for (unsigned long i = 0; i < num_fields; ++i) {
            delete static_cast<char *>(bind_[i].is_null);
            switch (bind_[i].buffer_type) {
                case MYSQL_TYPE_STRING:
                case MYSQL_TYPE_VAR_STRING:
                    delete[] static_cast<char *>(bind_[i].buffer);
                    break;
                case MYSQL_TYPE_LONG:
                    delete static_cast<int *>(bind_[i].buffer);
                    break;
                case MYSQL_TYPE_TIMESTAMP:
                case MYSQL_TYPE_DATETIME:
                    delete static_cast<MYSQL_TIME *>(bind_[i].buffer);
                    break;
            }
        }
        delete[] bind_;
    }
}

bool MysqlResult::next() {
    return mysql_stmt_fetch(stmt_) == 0;
}

string MysqlResult::getString(int column) const {
    if (bind_ == nullptr || column < 0 || column >= mysql_num_fields(result_)) {
        throw SQLException("Invalid column index", 0);
    }
    if (isNull(column)) {
        return "";
    }
    enum_field_types type = bind_[column].buffer_type;
    if (type == MYSQL_TYPE_STRING || type == MYSQL_TYPE_VAR_STRING) {
        return static_cast<char *>(bind_[column].buffer);
    } else if (type == MYSQL_TYPE_LONG) {
        return to_string(*static_cast<int *>(bind_[column].buffer));
    } else if (type == MYSQL_TYPE_DATETIME || type == MYSQL_TYPE_TIMESTAMP) {
        return timeToString(static_cast<MYSQL_TIME *>(bind_[column].buffer));
    }
    throw SQLException("Unknown type", 0);
}

int MysqlResult::getInt(int column) const {
    if (bind_ == nullptr || column < 0 || column >= mysql_num_fields(result_)) {
        throw SQLException("Invalid column index", 0);
    }
    if (isNull(column)) {
        return 0;
    }
    if (bind_[column].buffer_type != MYSQL_TYPE_LONG) {
        throw SQLException("Not int type", 0);
    }
    return *static_cast<int *>(bind_[column].buffer);
}

std::string MysqlResult::getColumnName(int column) const {
    if (result_ == nullptr || column < 0 || column >= mysql_num_fields(result_)) {
        throw SQLException("Invalid column index", 0);
    }
    MYSQL_FIELD *field = mysql_fetch_field_direct(result_, column);
    return field ? field->name : "";
}

string MysqlResult::timeToString(MYSQL_TIME *time) {
    return Format("{0}-{1}-{2} {3}:{4}:{5}", time->year, time->month, time->day, time->hour, time->minute,
                  time->second);
}
